﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Antlr.Runtime.Tree;
namespace GxcToCsharpParser.TreeWalkers.ControlFlowGraphBuilder
{
   public class LinearControlFlowBuilder
   {
      List<ControlFlowGraphNode> linearCfg;
      HashSet<int> hashCodes;
      int jumpLabelCounter = 0;
      Dictionary<string, List<int>> VariableUsage { get; set; }
      ImportantGxcFuntions gxcFuntionsToMonitor;

      /// <summary>
      /// Builds the specified tree.
      /// </summary>
      /// <param name="tree">The tree.</param>
      /// <returns></returns>
      public List<ControlFlowGraphNode> Build(CommonTree tree)
      {


         hashCodes = new HashSet<int>();
         linearCfg = new List<ControlFlowGraphNode>();
         VariableUsage = new Dictionary<string, List<int>>();
         gxcFuntionsToMonitor = new ImportantGxcFuntions();
         
         AddAstNodeToProgramScope(ControlFlowNodeType.Start, null);
         BreathFirstTreeWalk(tree);
         AddAstNodeToProgramScope(ControlFlowNodeType.End, null);
         hashCodes.Clear();
         return linearCfg;

         

      }
      private void BreathFirstTreeWalk(ITree tree)
      {
         if (tree == null)
            return;
         //process this node 
         ProcessCurrentAstTreeNode(tree);
         //process all children nodes 
         for (int i = 0; i < tree.ChildCount; i++)
         {
            ITree t = tree.GetChild(i);
            if (hashCodes.Contains(t.GetHashCode()) == false)
            {
               BreathFirstTreeWalk(t);
               if (hashCodes.Contains(t.GetHashCode()) == false)
                  hashCodes.Add(t.GetHashCode());
            }

         }



      }

      private void ProcessCurrentAstTreeNode(ITree tree)
      {
         switch (tree.Type)
         {
            case gxc2csharp.Translator.GxcToCTransform.GxcParser.IF:
               CreateIfNode(tree);
               break;

            case gxc2csharp.Translator.GxcToCTransform.GxcParser.ELSE:
               CreateElseNode(tree);
               break;
            case gxc2csharp.Translator.GxcToCTransform.GxcParser.SWITCH:
               CreateSwitchNode(tree);

               break;
            case gxc2csharp.Translator.GxcToCTransform.GxcParser.CASE:
               CreateCaseNode(tree);
               break;
            case gxc2csharp.Translator.GxcToCTransform.GxcParser.DEFAULT_CASE:
               CreateDefaultCaseNode(tree);
               break;
            case gxc2csharp.Translator.GxcToCTransform.GxcParser.FUNCTION_CALL:
               CreateFunctionCallNode(tree);
               break;
            case gxc2csharp.Translator.GxcToCTransform.GxcParser.ASSIGNMENT:
               CreateAssignmentNode(tree);
               break;
            case gxc2csharp.Translator.GxcToCTransform.GxcParser.WHILE:
               CreateWhileNode(tree);
               break;
            case gxc2csharp.Translator.GxcToCTransform.GxcParser.FOR:
               CreateForNode(tree);
               break;
             case gxc2csharp.Translator.GxcToCTransform.GxcParser.DO:
               CreateDoWhileNode(tree);
               break;
            case gxc2csharp.Translator.GxcToCTransform.GxcParser.BREAK:
               AddControlFlowGraphNodeToProgramScope(new ControlFlowGraphNode() { Type = ControlFlowNodeType.Break});
               break;
            case gxc2csharp.Translator.GxcToCTransform.GxcParser.CONTINUE:
               AddControlFlowGraphNodeToProgramScope( new ControlFlowGraphNode() { Type = ControlFlowNodeType.Continue });
               break;
         }

      }
      private void CreateDoWhileNode(ITree astTree)
      {
          hashCodes.Add(astTree.GetHashCode());
          ControlFlowGraphNode doloop = new ControlFlowGraphNode() { Type = ControlFlowNodeType.DoLoop, HashCodeOfNode = astTree.GetHashCode() };
          AddControlFlowGraphNodeToProgramScope(doloop);
          for (int i = 0; i < astTree.ChildCount; i++)
          {
              BreathFirstTreeWalk(astTree.GetChild(i));
          }

          AddControlFlowGraphNodeToProgramScope(doloop);
      }
      private void CreateForNode(ITree astTree)
      {
         hashCodes.Add(astTree.GetHashCode());
         
         ITree init= astTree.GetChild(0);
         BreathFirstTreeWalk(init);
         ControlFlowGraphNode ifnode = new ControlFlowGraphNode() { Type = ControlFlowNodeType.Loop ,HashCodeOfNode = astTree.GetHashCode()};
         AddControlFlowGraphNodeToProgramScope(ifnode);
         for (int i = 1; i < astTree.ChildCount; i++)
         {
            BreathFirstTreeWalk(astTree.GetChild(i));
         }
         AddControlFlowGraphNodeToProgramScope(ifnode);
      }

      private void CreateWhileNode(ITree astTree)
      {
         hashCodes.Add(astTree.GetHashCode());
         ControlFlowGraphNode ifnode = new ControlFlowGraphNode() { Type = ControlFlowNodeType.Loop, HashCodeOfNode = astTree.GetHashCode() };
         AddControlFlowGraphNodeToProgramScope(ifnode);
         ITree child = astTree.GetChild(0);
         BreathFirstTreeWalk(child);
         AddControlFlowGraphNodeToProgramScope(ifnode);
         

      }

      private void CreateDefaultCaseNode(ITree astTree)
      {
         hashCodes.Add(astTree.GetHashCode());
         AddAstNodeToProgramScope(ControlFlowNodeType.DefaultCase, astTree);
         ITree child = astTree.GetChild(1);
         BreathFirstTreeWalk(child);
      }

      private void CreateCaseNode(ITree astTree)
      {
         hashCodes.Add(astTree.GetHashCode());
         AddAstNodeToProgramScope(ControlFlowNodeType.Case, astTree);
         ITree codeBlock = astTree.GetChild(0);
         BreathFirstTreeWalk(codeBlock);

      }

      private void CreateSwitchNode(ITree astTree)
      {
         hashCodes.Add(astTree.GetHashCode());
         ITree lValue = astTree.GetChild(0);
         AddAstNodeToProgramScope(ControlFlowNodeType.Switch, astTree);
         IncrementBranchCounter();
         var jumpLabel = CurrentBranchCounter();
         var jumpNode = AddBranchNodeToProgramScope(jumpLabel);

         for (int i = 1; i < astTree.ChildCount; i++)
         {
            BreathFirstTreeWalk(astTree.GetChild(i));
         }
         AddControlFlowGraphNodeToProgramScope(jumpNode);
      }

      private void CreateAssignmentNode(ITree astTree)
      {
         hashCodes.Add(astTree.GetHashCode());
         ITree lValue = astTree.GetChild(0);
         ITree rValue = astTree.GetChild(1);
         bool isCfgNodeCreationRequried = true;
         //we will only consider if id = expression;
         if (lValue.Type == gxc2csharp.Translator.GxcToCTransform.GxcParser.VARIABLE)
         {
            ITree child = lValue.GetChild(0);
            if (VariableUsage.ContainsKey(child.Text) == false)
            {
               VariableUsage[child.Text] = new List<int>();

            }
            VariableUsage[child.Text].Add(child.Line);
            string nodeText = "";
            switch (rValue.Type)
            {
               case gxc2csharp.Translator.GxcToCTransform.GxcParser.FUNCTION_CALL:
                  nodeText = CreateFuntionCallFromFuntionCallAst(rValue);
                  if (gxcFuntionsToMonitor.IsFuntionImportant(nodeText) == false)
                     isCfgNodeCreationRequried = false;
                  break;
               case gxc2csharp.Translator.GxcToCTransform.GxcParser.VARIABLE:
                  nodeText = rValue.GetChild(0).Text;
                  break;
               default:
                  Console.WriteLine("rvalue expression not parsed yet");
                  break;


            }
            if (isCfgNodeCreationRequried)
            {
               var node = new ControlFlowGraphNode() { Type = ControlFlowNodeType.Assignment, HashCodeOfNode = astTree.GetHashCode(), Name = child.Text + "=" + nodeText };
               linearCfg.Add(node);
            }
         }






      }



      private string CreateFuntionCallFromFuntionCallAst(ITree astFuntionCall)
      {
         /* Needs to ignore unimportant funtions.
          * Needs to update varialbe usage if variable is used as an argument for the funtion.
          * 
          */
         string str = "";
         for (int i = 0; i < astFuntionCall.ChildCount; i++)
         {
            str += astFuntionCall.GetChild(i).Text;
         }
         return str;
      }

      private void CreateFunctionCallNode(ITree tree)
      {

         ITree funtion_call = tree.GetChild(0);

         return;
      }

      private void CreateElseNode(ITree astElse)
      {
         hashCodes.Add(astElse.GetHashCode());
         AddAstNodeToProgramScope(ControlFlowNodeType.Else, astElse);
         ITree child = astElse.GetChild(0);
         BreathFirstTreeWalk(child);

      }

      private void CreateIfNode(ITree astIf)
      {
         hashCodes.Add(astIf.GetHashCode());

         AddAstNodeToProgramScope(ControlFlowNodeType.If, astIf);
         IncrementBranchCounter();
         var jumpLabel = CurrentBranchCounter();
         var jumpNode = AddBranchNodeToProgramScope(jumpLabel);



         ITree parent = astIf.Parent;

         //ITree joinNode = parent.GetChild(astIf.ChildIndex + 1);

         ITree ifExpression = astIf.GetChild(0);
         ITree ifTrueStatement = astIf.GetChild(1);

         BreathFirstTreeWalk(ifTrueStatement); //now this could have a bunch of if else statements with in
         if (astIf.ChildCount < 2)
            AddControlFlowGraphNodeToProgramScope(jumpNode);
         if (astIf.ChildCount > 2)
         {
            ITree astElse = astIf.GetChild(2);
            if (astElse.Type == gxc2csharp.Translator.GxcToCTransform.GxcParser.ELSE)
            {
               //  programScope.RemoveAt(programScope.Count - 1);
               CreateElseNode(astElse);
            }
            else
               for (int i = 2; i < astIf.ChildCount; i++)
                  BreathFirstTreeWalk(astIf.GetChild(i));

         }
         AddControlFlowGraphNodeToProgramScope(jumpNode);
         return;

      }
      /// <summary>
      /// Pushes the ast node on program stack.
      /// </summary>
      /// <param name="nodeType">Type of the node.</param>
      /// <param name="ast">The ast.</param>
      private void AddAstNodeToProgramScope(ControlFlowNodeType nodeType, ITree ast)
      {
         ControlFlowGraphNode node = null;
         if (ast != null)
            node = new ControlFlowGraphNode() { Type = nodeType, HashCodeOfNode = ast.GetHashCode() };
         else
            node = new ControlFlowGraphNode() { Type = nodeType, HashCodeOfNode = nodeType.GetHashCode() };


         linearCfg.Add(node);
      }
      private ControlFlowGraphNode AddBranchNodeToProgramScope(int jumpLabel)
      {
         var node = new ControlFlowGraphNode() { Type = ControlFlowNodeType.BranchJoin, Name = jumpLabel.ToString(), HashCodeOfNode = ControlFlowNodeType.BranchJoin.GetHashCode() + jumpLabel };
         linearCfg.Add(node);
         return node;

      }
      private void AddControlFlowGraphNodeToProgramScope(ControlFlowGraphNode controlFlowGraphNode)
      {
         linearCfg.Add(controlFlowGraphNode);
      }
      private void IncrementBranchCounter()
      {
         jumpLabelCounter++;
      }
      private int CurrentBranchCounter()
      {
         return jumpLabelCounter;
      }
      public void WriteToFile(string filePath)
      {
         using (System.IO.StreamWriter writer = new System.IO.StreamWriter(filePath))
         {

            int line = 0;
            foreach (var node in linearCfg)
            {
               writer.WriteLine("{0}) {1}", line, node);
               line++;
            }
            writer.Close();
         }
      }

   }
}
